/*
 * $Id$
 */

package corejs.parser;

import corejs.stmt.Block;
import corejs.stmt.ExpressionStatement;
import corejs.JsScanner;
import corejs.Scanner;
import corejs.stmt.Statement;
import corejs.Token;
import corejs.TokenType;

import corejs.expr.Expression;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

import corejs.util.XmlPrinter;

import static corejs.JsTokenType.*;
import static corejs.parser.Precedence.*;
import corejs.rt.JsObjects;

/**
 * Operator Precedence Parser
 * @author pengyu
 */
public class PrattParser {

   private Scanner _scanner;
   private List<Token> _tokenBuffer;
   private Stack<Block> _blocks;

   public static PrattParser string(final String s) {
      return new PrattParser(new JsScanner(s));
   }

   public PrattParser(final Scanner scanner) {
      _scanner = scanner;
      _tokenBuffer = new ArrayList<Token>();

      _blocks = new Stack<Block>();
      _blocks.push(new Block(null));

      STRING.prefix(new StringParser());
      NAME.prefix(new NameParser());
      INTEGER.prefix(new IntParser());
      LONG.prefix(new LongParser());
      DECIMAL.prefix(new DecimalParser());
      FUNCTION.prefix(new FunctionParser());
      LEFT_BRACE.prefix(new ObjectParser());
      LEFT_PAREN.prefix(new GroupingParser());
      LEFT_BRACKET.prefix(new ArrayParser());

      TRUE.prefix(new BoolParser());
      FALSE.prefix(new BoolParser());
      
      NULL.prefix(new NullParser());
      
      final PrefixParser pp = new PrefixOperatorParser();
      BANG.prefix(pp);
      TYPEOF.prefix(pp);
      PLUS.prefix(pp);
      MINUS.prefix(pp);
      PLUS_PLUS.prefix(pp);
      MINUS_MINUS.prefix(pp);
     
      ASSIGN.infix(new AssignmentParser());
      PLUS.infix(new BinaryOperatorParser(SUM));
      MINUS.infix(new BinaryOperatorParser(SUM));
      STAR.infix(new BinaryOperatorParser(PRODUCT));
      AND.infix(new BinaryOperatorParser(CONDITIONAL));
      OR.infix(new BinaryOperatorParser(CONDITIONAL));
      LEFT_PAREN.infix(new InvocationParser());
      DOT.infix(new BinaryOperatorParser(REFINEMENT));
      LEFT_BRACKET.infix(new BracketRefinementParser());
      COMMA.infix(new SequenceParser());

      final BinaryOperatorParser rel = new BinaryOperatorParser(RELATIONAL);
      LT.infix(rel);
      LE.infix(rel);
      GT.infix(rel);
      GE.infix(rel);
      EQUAL.infix(rel);
      STRICT_EQUAL.infix(rel);
      NOT_EQUAL.infix(rel);
      STRICT_NOT_EQUAL.infix(rel);
      
      final InfixParser pfx = new PostfixOperatorParser();
      PLUS_PLUS.infix(pfx);
      MINUS_MINUS.infix(pfx);

      QUESTION_MARK.infix(new ConditionalOperatorParser());

      VAR.statement(new VarParser());
      FUNCTION.statement(new FunctionParser());
      IF.statement(new IfParser());
      RETURN.statement(new ReturnParser());
      WHILE.statement(new WhileParser());
      BREAK.statement(new BreakParser());
      CONTINUE.statement(new ContinueParser());
      FOR.statement(new ForParser());

//      JsObjects.createGlobalObject();
   }

   public static void main(final String... args) {
      final PrattParser p = new PrattParser(
        new JsScanner("var wux = 99;function foo(a, b) {var col = \"cool\"; var i = 100; function bar(){}; var zac = function(m,n){var k = m - (n - (l + q)); function ABC(){var j = m + \"hello\";}}} var obj = {k1:\"v1\", \"k2\":abc, dd:a=2, k3:!yes,}; foo(1,abc,\"calling me\");foo()"));
      new XmlPrinter().print(p.parse()).print();
   }

   public Block parse() {
      return block(EOF);
   }

   public Statement statement() {
      return statement(nextToken());
   }

   public Statement statement(final Token tk) {
      final StatementParser p = tk.getStatementParser();
      if (null == p) {
         pushBack(tk);
         final Statement s = new ExpressionStatement(expr());
         match(SEMICOLON);
         return s;
      }
      else {
         return p.statement(this);
      }
   }

   public Expression expr(){
      return expr(0);
   }

   public Expression expr(final int precedence) {
      Token tk = nextToken();
      Expression left = tk.getPrefixParser().expr(this, tk);
      while (precedence < getPrecedence()) {
         tk = nextToken();
         left = tk.getInfixParser().expr(this, left, tk);
      }
      return left;
   }

   public Block block(final TokenType end) {
      final Block current = new Block(_blocks.peek());
      _blocks.push(current);
      for (Token tk = nextToken(); !tk.is(end); tk = nextToken()) {
         statement(tk).addTo(current);
         match(SEMICOLON);
      }
      return _blocks.pop();
   }

   public Block currentBlock() {
      return _blocks.peek();
   }

   public Token nextToken(final TokenType expected) {
      final Token tk = lookAhead(0);
      if (tk.type() != expected) {
         throw new ParseException(String.format("expected:%s, got:%s", 
                                                 expected, tk.toString()));
      }
      return _tokenBuffer.remove(0);
   }

   public Token nextToken() {
      lookAhead(0);
      return _tokenBuffer.remove(0);
   }

   public boolean match(final TokenType expected) {
      final Token tk = lookAhead(0);
      if (tk.type() != expected) {
         return false;
      }
      _tokenBuffer.remove(0);
      return true;
   }

   protected Expression matchAndParse(final TokenType expected) {
      Expression e = null;
      if (!match(expected)) {
         e = expr();
         nextToken(expected);
      }
      return e;
   }

   private void pushBack(final Token tk) {
      _tokenBuffer.add(null);
      for (int i =  _tokenBuffer.size() - 1;  i > 0;) {
         _tokenBuffer.set(i, _tokenBuffer.get(--i));
      }
      _tokenBuffer.set(0, tk);
   }

   private Token lookAhead(final int count) {
      while (count >= _tokenBuffer.size()) {
         _tokenBuffer.add(_scanner.nextToken());
      }
      return _tokenBuffer.get(count);
   }

   private int getPrecedence() {
      final InfixParser parser = lookAhead(0).getInfixParser();
      return null != parser ? parser.getPrecedence() : 0;
   }
}
